package com.fansl.allround.auth.config;

import com.fansl.allround.auth.service.AllroundClientDetailsService;
import com.fansl.allround.auth.service.SysUserDetailsServiceImpl;
import com.fansl.allround.common.core.constant.SecurityConstants;
import com.fansl.allround.common.core.constant.enums.ServiceTypeEnum;
import com.fansl.allround.common.security.component.AllroundWebResponseExceptionTranslator;
import com.fansl.allround.common.security.service.AllroundSysUser;
import com.fansl.allround.common.security.service.AllroundUcenterUser;
import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.oauth2.common.DefaultOAuth2AccessToken;
import org.springframework.security.oauth2.config.annotation.configurers.ClientDetailsServiceConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configuration.AuthorizationServerConfigurerAdapter;
import org.springframework.security.oauth2.config.annotation.web.configuration.EnableAuthorizationServer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerEndpointsConfigurer;
import org.springframework.security.oauth2.config.annotation.web.configurers.AuthorizationServerSecurityConfigurer;
import org.springframework.security.oauth2.provider.token.DefaultTokenServices;
import org.springframework.security.oauth2.provider.token.TokenEnhancer;
import org.springframework.security.oauth2.provider.token.TokenStore;
import org.springframework.security.oauth2.provider.token.store.redis.RedisTokenStore;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * @author fansl
 * @Description: 认证服务器配置
 * @date 2019/7/16 20:12
 */
@Configuration
@AllArgsConstructor
@EnableAuthorizationServer
public class AuthorizationServerConfig extends AuthorizationServerConfigurerAdapter {
    private final DataSource dataSource;
    private final AuthenticationManager authenticationManager;
    private final RedisConnectionFactory redisConnectionFactory;
    private final SysUserDetailsServiceImpl userDetailsService;

    /**
     * 配置OAuth2的客户端相关信息
     *
     * @param clients
     * @throws Exception
     */
    @Override
    public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
//        AllroundClientDetailsService clientDetailsService = new AllroundClientDetailsService(dataSource);
//        clientDetailsService
//                .setSelectClientDetailsSql(SecurityConstants.DEFAULT_SELECT_STATEMENT);
//        clientDetailsService
//                .setFindClientDetailsSql(SecurityConstants.DEFAULT_FIND_STATEMENT);

        AllroundClientDetailsService clientDetailsService = new AllroundClientDetailsService();
        clients.withClientDetails(clientDetailsService);
    }

    /**
     * 用来配置令牌端点(Token Endpoint)的安全约束.
     * 配置AuthorizationServer安全认证的相关信息，
     * 创建ClientCredentialsTokenEndpointFilter核心过滤器
     *
     * @param security
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerSecurityConfigurer security) throws Exception {
        //allowFormAuthenticationForClients是为了注册clientCredentialsTokenEndpointFilter
        //clientCredentialsTokenEndpointFilter,解析request中的client_id和client_secret
        //构造成UsernamePasswordAuthenticationToken,然后通过UserDetailsService查询作简单的认证而已
        //一般是针对password模式和client_credentials
        //当然也可以使用http basic认证
        //如果使用了http basic认证,就不用使用clientCredentialsTokenEndpointFilter
        //因为本质是一样的
        security.allowFormAuthenticationForClients()
                // 开启/oauth/check_token验证端口无权限访问
                .checkTokenAccess("permitAll()");
    }

    /**
     * 用来配置授权（authorization）以及令牌（token）的访问端点和令牌服务(token services)。
     * 配置AuthorizationServerEndpointsConfigurer众多相关类，
     * 包括配置身份认证器，配置认证方式，TokenStore，TokenGranter，OAuth2RequestFactory
     *
     * @param endpoints
     * @throws Exception
     */
    @Override
    public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
        endpoints.allowedTokenEndpointRequestMethods(HttpMethod.GET, HttpMethod.POST)
                .tokenStore(tokenStore())
                //当你设置了这个东西（即 TokenGranter 接口实现），那么授权将会交由你来完全掌控，
                // 并且会忽略掉上面的这几个属性，这个属性一般是用作拓展用途的，
                // 即标准的四种授权模式已经满足不了你的需求的时候，才会考虑使用这个。
                .tokenEnhancer(tokenEnhancer())
                //如果啊，你设置了这个属性的话，那说明你有一个自己的 UserDetailsService 接口的实现，
                // 或者你可以把这个东西设置到全局域上面去（例如 GlobalAuthenticationManagerConfigurer 这个配置对象），
                // 当你设置了这个之后，那么 "refresh_token" 即刷新令牌授权类型模式的流程中就会包含一个检查，
                // 用来确保这个账号是否仍然有效，假如说你禁用了这个账户的话。
                .userDetailsService(userDetailsService)
                //认证管理器，当你选择了资源所有者密码（password）授权类型的时候，
                // 请设置这个属性注入一个 AuthenticationManager 对象
                .authenticationManager(authenticationManager)
                //这个属性是用来设置授权码服务的（即 AuthorizationCodeServices 的实例对象），
                // 主要用于 "authorization_code" 授权码类型模式。
//                .authorizationCodeServices(null)
                .reuseRefreshTokens(false)
                .exceptionTranslator(new AllroundWebResponseExceptionTranslator());
        //配置tokenServices
        DefaultTokenServices tokenServices = new DefaultTokenServices();
        tokenServices.setTokenStore(endpoints.getTokenStore());
        tokenServices.setSupportRefreshToken(true);
        tokenServices.setClientDetailsService(endpoints.getClientDetailsService());
        tokenServices.setTokenEnhancer(endpoints.getTokenEnhancer());
        //token的有效期1天
        tokenServices.setAccessTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(1));
        //刷新token的有效期1天
        tokenServices.setRefreshTokenValiditySeconds((int) TimeUnit.DAYS.toSeconds(1));
        tokenServices.setReuseRefreshToken(false);
        endpoints.tokenServices(tokenServices);
    }

    @Bean
    public TokenStore tokenStore() {
        RedisTokenStore tokenStore = new RedisTokenStore(redisConnectionFactory);
        tokenStore.setPrefix(SecurityConstants.PROJECT_PREFIX + SecurityConstants.OAUTH_PREFIX);
        return tokenStore;
    }

    @Bean
    public TokenEnhancer tokenEnhancer() {
        return (accessToken, authentication) -> {
            final Map<String, Object> additionalInfo = new HashMap<>(4);
            Object principal = authentication.getUserAuthentication().getPrincipal();
            if (principal instanceof AllroundSysUser) {
                AllroundSysUser allroundSysUser = (AllroundSysUser) principal;
                additionalInfo.put(SecurityConstants.DETAILS_LICENSE, SecurityConstants.PROJECT_LICENSE);
                additionalInfo.put(SecurityConstants.DETAILS_USER_ID, allroundSysUser.getId());
                additionalInfo.put(SecurityConstants.DETAILS_USERNAME, allroundSysUser.getUsername());
                additionalInfo.put(SecurityConstants.DETAILS_DEPT_ID, allroundSysUser.getDeptId());
                additionalInfo.put(SecurityConstants.DETAILS_SERVICE_NAME, ServiceTypeEnum.BACKEND.name());
                ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
            }
            if (principal instanceof AllroundUcenterUser) {
                AllroundUcenterUser allroundUcenterUser = (AllroundUcenterUser) principal;
                additionalInfo.put(SecurityConstants.DETAILS_LICENSE, SecurityConstants.PROJECT_LICENSE);
                additionalInfo.put(SecurityConstants.DETAILS_USER_ID, allroundUcenterUser.getUserId());
                additionalInfo.put(SecurityConstants.DETAILS_USERNAME, allroundUcenterUser.getUsername());
                additionalInfo.put(SecurityConstants.DETAILS_SERVICE_NAME, ServiceTypeEnum.UCENTER.name());
                ((DefaultOAuth2AccessToken) accessToken).setAdditionalInformation(additionalInfo);
            }
            return accessToken;
        };
    }
}
